package com.sky.aspect;

import com.sky.aspect.annotation.AutoFill;
import com.sky.aspect.annotation.InsertAutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面，实现公共字段自动填充处理逻辑
 * @author yangkexuan
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.sky.aspect.annotation.AutoFill) ||" +
            "@annotation(com.sky.aspect.annotation.InsertAutoFill) ||" +
            "@annotation(com.sky.aspect.annotation.UpdateAutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知，在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        //取第一个对象，如果为空直接返回
        if(args == null || args.length == 0){
            return;
        }

        //取第一个对象,因为args是个数组  entity ==> Dish(id=46, name=王老吉, categoryId=11, price=6, image=https://sky-itcast.oss-cn-beijing.aliyuncs.com/41bfcacf-7ad4-4927-8b26-df366553a94c.png, description=, status=1, createTime=null, updateTime=null, createUser=null, updateUser=null)
        Object entity = args[0];
        //方法签名对象 Method对象代表了一个类或者接口的方法，包含该方法所有信息(包括访问修饰符，返回值类型，参数类型，方法名称)
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

            try {
                //准备赋值的数据
                LocalDateTime now = LocalDateTime.now();
                Long currentId = BaseContext.getCurrentId();

                //Object 的运行时类  clazz==>class com.sky.entity.Dish
                Class<?> clazz = entity.getClass();

                // .isAnnotationPresent()  判断方法上有没有指定的注解，LogAnnotation.class 表示获取 LogAnnotation 类对应的 Class 对象
                if(method.isAnnotationPresent(InsertAutoFill.class)){
                    autoInsert(entity,currentId,now,clazz);
                }
                // 修改注解
                if(method.isAnnotationPresent(InsertAutoFill.class)){
                    autoUpdate(entity,currentId,now,clazz);
                }
                if(method.isAnnotationPresent(AutoFill.class)){
                    AutoFill autoFill = method.getAnnotation(AutoFill.class);
                    OperationType operationType = autoFill.value();
                    if(OperationType.INSERT.equals(operationType)){
                        autoInsert(entity,currentId,now,clazz);
                    }
                    if(OperationType.UPDATE.equals(operationType)){
                        autoUpdate(entity,currentId,now,clazz);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
                log.error("公共字段自动赋值失败");
            }

    }

    /**
     * 自动新增赋值 为公共字段赋值
     *
     * @param entity 实体类
     * @param currentId 操作人
     * @param now 时间
     * @param clazz 实体类的类型
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void autoInsert(Object entity, Long currentId, LocalDateTime now, Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        //获取赋值的方法
        //getDeclaredMethod(String name, Class<?>... parameterTypes) 方法是 Class 类中的一个方法，用于返回一个方法对象，该对象表示该类或接口上具有指定名称和参数类型的方法，包括私有方法、受保护方法、包可见方法和公共方法。
        //name:方法名称， parameterTypes:方法的参数类型。
        //如果没有找到符合条件的方法，则会抛出 NoSuchMethodException异常。
        //此处是获取setCreateTime()这个方法
        Method setUpdateTime = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //为公共字段赋值
        //invoke(Object obj, Object…args)：调用类中的方法
        // obj（对象），在我们平常使用过程中用到的是类，类是对象的一个集合
        // args:用于方法调用使用的参数，是调用invoke这个方法所使用的参数，我们使用是一般是类中的方法（method）
        // 因此invoke方法我们也可以这样理解：invoke（class，method），相当于把类中的方法参数化了。
        setUpdateTime.invoke(entity, now);
        setUpdateUser.invoke(entity, currentId);
    }

    /**
     * 自动更新赋值
     *
     * @param entity
     * @param currentId
     * @param now
     * @param clazz
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void autoUpdate(Object entity, Long currentId, LocalDateTime now, Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method setCreateTime = clazz.getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
        Method setCreateUser = clazz.getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        Method setUpdateTime = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        setCreateTime.invoke(entity, now);
        setCreateUser.invoke(entity, currentId);
        setUpdateTime.invoke(entity, now);
        setUpdateUser.invoke(entity, currentId);
    }
}